﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Threading;

#if !SILVERLIGHT
using System.Windows;
#endif

namespace Xceed.Silverlight.Utils
{
  [DebuggerStepThrough]
  internal abstract partial class WeakEventManager
  {
    #region Static Fields

    private static readonly object SyncRoot = new object();
    private static readonly Dictionary<Type, WeakEventManager> Managers = new Dictionary<Type, WeakEventManager>();
    private static DispatcherOperation CleanupDispatcherOperation; //null

    #endregion

    #region Constructor

    protected WeakEventManager()
    {
    }

    #endregion

    protected static void SetCurrentManager( Type managerType, WeakEventManager manager )
    {
      if( managerType == null )
        throw new ArgumentNullException( "managerType" );

      if( manager == null )
        throw new ArgumentNullException( "manager" );

      lock( WeakEventManager.SyncRoot )
      {
        WeakEventManager.Managers.Add( managerType, manager );
      }
    }

    protected void DeliverEvent( object sender, EventArgs args )
    {
      if( sender == null )
        throw new ArgumentNullException( "sender" );

      if( args == null )
        throw new ArgumentNullException( "args" );

      Type managerType = this.GetType();
      WeakEventManager registeredManager = WeakEventManager.GetCurrentManager( managerType );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to deliver an event using a manager other than the current one." );

      lock( m_syncRoot )
      {
        var listeners = this.GetListeners( sender );

        if( WeakEventManager.IsEmpty( listeners ) )
        {
          WeakEventManager.ScheduleCleanup();
        }
        else
        {
          foreach( var listener in listeners.ToArray() )
          {
            var target = listener.Target as IWeakEventListener;

            if( target != null )
            {
              target.ReceiveWeakEvent( managerType, sender, args );
            }
            else
            {
              WeakEventManager.ScheduleCleanup();
            }
          }
        }
      }
    }

    protected void AddListenerProtected( object source, IWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      WeakEventManager registeredManager = WeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to add a listener using a manager other than the current one." );

      lock( m_syncRoot )
      {
        List<WeakReference> listeners = this.GetListeners( source );
        WeakReference newListener = new WeakReference( listener );

        if( listeners == null )
        {
          listeners = new List<WeakReference>( 1 );
          listeners.Add( newListener );

          m_bindings.Add( new WeakReference( source ), listeners );

          this.StartListening( source );
        }
        else
        {
          listeners.Add( newListener );
        }
      }

      WeakEventManager.ScheduleCleanup();
    }

    protected void RemoveListenerProtected( object source, IWeakEventListener listener )
    {
      if( source == null )
        throw new ArgumentNullException( "source" );

      if( listener == null )
        throw new ArgumentNullException( "listener" );

      WeakEventManager registeredManager = WeakEventManager.GetCurrentManager( this.GetType() );

      if( registeredManager != this )
        throw new InvalidOperationException( "An attempt was made to remove a listener using a manager other than the current one." );

      lock( m_syncRoot )
      {
        var entry = ( from binding in m_bindings
                      where binding.Key.Target == source
                      select new
                       {
                         Source = binding.Key,
                         Listeners = binding.Value
                       } ).FirstOrDefault();

        if( entry != null )
        {
          var listeners = entry.Listeners;
          int removeAt = 0;

          while( removeAt < listeners.Count )
          {
            var item = listeners[ removeAt ];
            bool found = ( item.Target == listener );

            if( found || !item.IsAlive )
            {
              listeners.RemoveAt( removeAt );

              if( found )
                break;
            }
            else
            {
              removeAt++;
            }
          }

          if( listeners.Count == 0 )
          {
            m_bindings.Remove( entry.Source );

            this.StopListening( source );
          }
        }
      }

      WeakEventManager.ScheduleCleanup();
    }

    protected abstract void StartListening( object source );

    protected abstract void StopListening( object source );

    private static void ScheduleCleanup()
    {
      if( WeakEventManager.CleanupDispatcherOperation != null )
        return;

      lock( WeakEventManager.SyncRoot )
      {
        if( WeakEventManager.CleanupDispatcherOperation != null )
          return;

        try
        {
          Dispatcher dispatcher = WeakEventManager.GetCurrentDispatcher();
          if( dispatcher == null )
            return;

          WeakEventManager.CleanupDispatcherOperation = WeakEventManager.DispatchCleanUp( dispatcher, new Action( WeakEventManager.Cleanup ) );
        }
        catch
        {
        }
      }
    }

    private static void Cleanup()
    {
      IEnumerable<WeakEventManager> managers;

      lock( WeakEventManager.SyncRoot )
      {
        WeakEventManager.CleanupDispatcherOperation = null;

        managers = WeakEventManager.Managers.Values.ToArray();
      }

      foreach( var manager in managers )
      {
        manager.CleanUp();
      }
    }

    private static WeakEventManager GetCurrentManager( Type managerType )
    {
      lock( WeakEventManager.SyncRoot )
      {
        WeakEventManager manager;

        if( !WeakEventManager.Managers.TryGetValue( managerType, out manager ) )
        {
          manager = null;
        }

        return manager;
      }
    }

    private static bool IsEmpty<T>( IEnumerable<T> source )
    {
      return ( source == null )
          || ( !source.Any() );
    }

    private List<WeakReference> GetListeners( object source )
    {
      return ( from binding in m_bindings
               where binding.Key.Target == source
               select binding.Value ).FirstOrDefault();
    }

    private void CleanUp()
    {
      lock( m_syncRoot )
      {
        var emptySources = new List<WeakReference>( 0 );

        foreach( var binding in m_bindings )
        {
          bool alive = binding.Key.IsAlive;

          if( alive )
          {
            var listeners = binding.Value;

            for( int i = listeners.Count - 1; i >= 0; i-- )
            {
              if( !listeners[ i ].IsAlive )
              {
                listeners.RemoveAt( i );
              }
            }

            alive = listeners.Any();
          }

          if( !alive )
          {
            emptySources.Add( binding.Key );
          }
        }

        foreach( var source in emptySources )
        {
          m_bindings.Remove( source );

          object target = source.Target;
          if( target != null )
          {
            this.StopListening( target );
          }
        }
      }
    }

    #region Private Fields

    private readonly object m_syncRoot = new object();
    private readonly Dictionary<WeakReference, List<WeakReference>> m_bindings = new Dictionary<WeakReference, List<WeakReference>>();

    #endregion
  }
}
